/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Buffer map tests.
 *//*--------------------------------------------------------------------*/

#include "es3fBufferMapTests.hpp"
#include "glsBufferTestUtil.hpp"
#include "tcuTestLog.hpp"
#include "deMemory.h"
#include "deString.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>

using std::set;
using std::vector;
using std::string;
using tcu::TestLog;

namespace deqp
{
namespace gles3
{
namespace Functional
{

using namespace gls::BufferTestUtil;

// Test cases.

class BufferMapReadCase : public BufferCase
{
public:
	BufferMapReadCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, WriteType write)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_bufferSize		(bufferSize)
		, m_mapOffset		(mapOffset)
		, m_mapSize			(mapSize)
		, m_write			(write)
	{
	}

	IterateResult iterate (void)
	{
		TestLog&		log			= m_testCtx.getLog();
		deUint32		dataSeed	= deStringHash(getName());
		ReferenceBuffer	refBuf;
		BufferWriter	writer		(m_renderCtx, m_testCtx.getLog(), m_write);
		bool			isOk		= false;

		// Setup reference data.
		refBuf.setSize(m_bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

		deUint32 buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, m_bufferSize, DE_NULL, m_usage);
		writer.write(buf, 0, m_bufferSize, refBuf.getPtr(), m_bufferTarget);

		glBindBuffer(m_bufferTarget, buf);
		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		isOk = compareByteArrays(log, (const deUint8*)ptr, refBuf.getPtr(m_mapOffset), m_mapSize);

		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	int				m_bufferSize;
	int				m_mapOffset;
	int				m_mapSize;
	WriteType		m_write;
};

class BufferMapWriteCase : public BufferCase
{
public:
	BufferMapWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int size, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_size			(size)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		deUint32		dataSeed	= deStringHash(getName());
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);

		// Setup reference data.
		refBuf.setSize(m_size);
		fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);

		deUint32 buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, m_size, DE_NULL, m_usage);

		void* ptr = glMapBufferRange(m_bufferTarget, 0, m_size, GL_MAP_WRITE_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		fillWithRandomBytes((deUint8*)ptr, m_size, dataSeed);
		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_bufferTarget);
		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	int				m_size;
	VerifyType		m_verify;
};

class BufferPartialMapWriteCase : public BufferCase
{
public:
	BufferPartialMapWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_bufferSize		(bufferSize)
		, m_mapOffset		(mapOffset)
		, m_mapSize			(mapSize)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		deUint32		dataSeed	= deStringHash(getName());
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);

		// Setup reference data.
		refBuf.setSize(m_bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

		deUint32 buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);

		// Do reference map.
		fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed&0xabcdef);

		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_WRITE_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);
		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget);
		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	int				m_bufferSize;
	int				m_mapOffset;
	int				m_mapSize;
	VerifyType		m_verify;
};

class BufferMapInvalidateCase : public BufferCase
{
public:
	BufferMapInvalidateCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_partialWrite	(partialWrite)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		deUint32		dataSeed		= deStringHash(getName());
		deUint32		buf				= 0;
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
		const int		bufferSize		= 1300;
		const int		mapOffset		= 200;
		const int		mapSize			= 1011;
		const int		mapWriteOffs	= m_partialWrite ? 91 : 0;
		const int		verifyOffset	= mapOffset+mapWriteOffs;
		const int		verifySize		= mapSize-mapWriteOffs;

		// Setup reference data.
		refBuf.setSize(bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

		buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

		// Do reference map.
		fillWithRandomBytes(refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs, dataSeed&0xabcdef);

		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_BUFFER_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		deMemcpy((deUint8*)ptr+mapWriteOffs, refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs);
		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	bool			m_partialWrite;
	VerifyType		m_verify;
};

class BufferMapPartialInvalidateCase : public BufferCase
{
public:
	BufferMapPartialInvalidateCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_partialWrite	(partialWrite)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		deUint32		dataSeed		= deStringHash(getName());
		deUint32		buf				= 0;
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
		const int		bufferSize		= 1300;
		const int		mapOffset		= 200;
		const int		mapSize			= 1011;
		const int		mapWriteOffs	= m_partialWrite ? 91						: 0;
		const int		verifyOffset	= m_partialWrite ? mapOffset+mapWriteOffs	: 0;
		const int		verifySize		= bufferSize-verifyOffset;

		// Setup reference data.
		refBuf.setSize(bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

		buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

		// Do reference map.
		fillWithRandomBytes(refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs, dataSeed&0xabcdef);

		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		deMemcpy((deUint8*)ptr+mapWriteOffs, refBuf.getPtr(mapOffset+mapWriteOffs), mapSize-mapWriteOffs);
		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	bool			m_partialWrite;
	VerifyType		m_verify;
};

class BufferMapExplicitFlushCase : public BufferCase
{
public:
	BufferMapExplicitFlushCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, bool partialWrite, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_partialWrite	(partialWrite)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		deUint32		dataSeed		= deStringHash(getName());
		deUint32		buf				= 0;
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier		(m_renderCtx, m_testCtx.getLog(), m_verify);
		const int		bufferSize		= 1300;
		const int		mapOffset		= 200;
		const int		mapSize			= 1011;
		const int		sliceAOffs		= m_partialWrite ? 1		: 0;
		const int		sliceASize		= m_partialWrite ? 96		: 473;
		const int		sliceBOffs		= m_partialWrite ? 503		: sliceAOffs+sliceASize;
		const int		sliceBSize		= mapSize-sliceBOffs;
		bool			isOk			= true;

		// Setup reference data.
		refBuf.setSize(bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

		buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

		// Do reference map.
		fillWithRandomBytes(refBuf.getPtr(mapOffset), mapSize, dataSeed&0xabcdef);

		void* ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT|GL_MAP_FLUSH_EXPLICIT_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		deMemcpy(ptr, refBuf.getPtr(mapOffset), mapSize);

		glFlushMappedBufferRange(m_bufferTarget, sliceAOffs, sliceASize);
		GLU_CHECK_MSG("glFlushMappedBufferRange");
		glFlushMappedBufferRange(m_bufferTarget, sliceBOffs, sliceBSize);
		GLU_CHECK_MSG("glFlushMappedBufferRange");

		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		if (m_partialWrite)
		{
			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset+sliceAOffs, sliceASize, m_bufferTarget))
				isOk = false;

			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset+sliceBOffs, sliceBSize, m_bufferTarget))
				isOk = false;
		}
		else
		{
			if (!verifier.verify(buf, refBuf.getPtr(), mapOffset, mapSize, m_bufferTarget))
				isOk = false;
		}

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	bool			m_partialWrite;
	VerifyType		m_verify;
};

class BufferMapUnsyncWriteCase : public BufferCase
{
public:
	BufferMapUnsyncWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
	{
	}

	IterateResult iterate (void)
	{
		VertexArrayVerifier	verifier	(m_renderCtx, m_testCtx.getLog());
		deUint32			dataSeed	= deStringHash(getName());
		ReferenceBuffer		refBuf;
		deUint32			buf			= 0;
		bool				isOk		= true;
		const int			size		= 1200;

		// Setup reference data.
		refBuf.setSize(size);
		fillWithRandomBytes(refBuf.getPtr(), size, dataSeed);

		buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, size, refBuf.getPtr(), m_usage);

		// Use for rendering.
		if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
			isOk = false;
		// \note ReadPixels() implies Finish

		glBindBuffer(m_bufferTarget, buf);
		void* ptr = glMapBufferRange(m_bufferTarget, 0, size, GL_MAP_WRITE_BIT|GL_MAP_UNSYNCHRONIZED_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		fillWithRandomBytes(refBuf.getPtr(), size, dataSeed&0xabcdef);
		deMemcpy(ptr, refBuf.getPtr(), size);

		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		// Synchronize.
		glFinish();

		if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
			isOk = false;

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
};

class BufferMapReadWriteCase : public BufferCase
{
public:
	BufferMapReadWriteCase (Context& context, const char* name, const char* desc, deUint32 bufferTarget, deUint32 usage, int bufferSize, int mapOffset, int mapSize, VerifyType verify)
		: BufferCase		(context.getTestContext(), context.getRenderContext(), name, desc)
		, m_bufferTarget	(bufferTarget)
		, m_usage			(usage)
		, m_bufferSize		(bufferSize)
		, m_mapOffset		(mapOffset)
		, m_mapSize			(mapSize)
		, m_verify			(verify)
	{
	}

	IterateResult iterate (void)
	{
		TestLog&		log			= m_testCtx.getLog();
		deUint32		dataSeed	= deStringHash(getName());
		deUint32		buf			= 0;
		ReferenceBuffer	refBuf;
		BufferVerifier	verifier	(m_renderCtx, m_testCtx.getLog(), m_verify);
		bool			isOk		= true;

		// Setup reference data.
		refBuf.setSize(m_bufferSize);
		fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

		buf = genBuffer();
		glBindBuffer(m_bufferTarget, buf);
		glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);

		// Verify before mapping.
		if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
			isOk = false;

		glBindBuffer(m_bufferTarget, buf);
		void* ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT|GL_MAP_WRITE_BIT);
		GLU_CHECK_MSG("glMapBufferRange");
		TCU_CHECK(ptr);

		// Compare mapped ptr.
		if (!compareByteArrays(log, (const deUint8*)ptr, refBuf.getPtr(m_mapOffset), m_mapSize))
			isOk = false;

		fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed&0xabcdef);
		deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);

		glUnmapBuffer(m_bufferTarget);
		GLU_CHECK_MSG("glUnmapBuffer");

		// Compare final buffer.
		if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
			isOk = false;

		deleteBuffer(buf);

		m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
								isOk ? "Pass"				: "Buffer verification failed");
		return STOP;
	}

private:
	deUint32		m_bufferTarget;
	deUint32		m_usage;
	int				m_bufferSize;
	int				m_mapOffset;
	int				m_mapSize;
	VerifyType		m_verify;
};

BufferMapTests::BufferMapTests (Context& context)
	: TestCaseGroup(context, "map", "Buffer map tests")
{
}

BufferMapTests::~BufferMapTests (void)
{
}

void BufferMapTests::init (void)
{
	static const deUint32 bufferTargets[] =
	{
		GL_ARRAY_BUFFER,
		GL_COPY_READ_BUFFER,
		GL_COPY_WRITE_BUFFER,
		GL_ELEMENT_ARRAY_BUFFER,
		GL_PIXEL_PACK_BUFFER,
		GL_PIXEL_UNPACK_BUFFER,
		GL_TRANSFORM_FEEDBACK_BUFFER,
		GL_UNIFORM_BUFFER
	};

	static const deUint32 usageHints[] =
	{
		GL_STREAM_DRAW,
		GL_STREAM_READ,
		GL_STREAM_COPY,
		GL_STATIC_DRAW,
		GL_STATIC_READ,
		GL_STATIC_COPY,
		GL_DYNAMIC_DRAW,
		GL_DYNAMIC_READ,
		GL_DYNAMIC_COPY
	};

	static const struct
	{
		const char*		name;
		WriteType		write;
	} bufferDataSources[] =
	{
		{ "sub_data",		WRITE_BUFFER_SUB_DATA	},
		{ "map_write",		WRITE_BUFFER_WRITE_MAP	}
	};

	static const struct
	{
		const char*		name;
		VerifyType		verify;
	} bufferUses[] =
	{
		{ "map_read",				VERIFY_BUFFER_READ_MAP	},
		{ "render_as_vertex_array",	VERIFY_AS_VERTEX_ARRAY	},
		{ "render_as_index_array",	VERIFY_AS_INDEX_ARRAY	}
	};

	// .read
	{
		tcu::TestCaseGroup* mapReadGroup = new tcu::TestCaseGroup(m_testCtx, "read", "Buffer read using glMapBufferRange()");
		addChild(mapReadGroup);

		// .[data src]
		for (int srcNdx = 0; srcNdx < DE_LENGTH_OF_ARRAY(bufferDataSources); srcNdx++)
		{
			WriteType			write		= bufferDataSources[srcNdx].write;
			tcu::TestCaseGroup* writeGroup	= new tcu::TestCaseGroup(m_testCtx, bufferDataSources[srcNdx].name, "");
			mapReadGroup->addChild(writeGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				const deUint32	hint		= GL_STATIC_READ;
				const int		size		= 1019;
				const int		partialOffs	= 17;
				const int		partialSize	= 501;

				writeGroup->addChild(new BufferMapReadCase(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),		"", target, hint, size, 0, size, write));
				writeGroup->addChild(new BufferMapReadCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, write));
			}
		}

		// .usage_hints
		{
			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Different usage hints with glMapBufferRange()");
			mapReadGroup->addChild(hintsGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
				{
					deUint32		target		= bufferTargets[targetNdx];
					deUint32		hint		= usageHints[hintNdx];
					const int		size		= 1019;
					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

					hintsGroup->addChild(new BufferMapReadCase(m_context, name.c_str(), "", target, hint, size, 0, size, WRITE_BUFFER_SUB_DATA));
				}
			}
		}
	}

	// .write
	{
		tcu::TestCaseGroup* mapWriteGroup = new tcu::TestCaseGroup(m_testCtx, "write", "Buffer write using glMapBufferRange()");
		addChild(mapWriteGroup);

		// .[verify type]
		for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
		{
			VerifyType			verify		= bufferUses[useNdx].verify;
			tcu::TestCaseGroup* useGroup	= new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
			mapWriteGroup->addChild(useGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;
				const int		size		= 1019;
				const int		partialOffs	= 17;
				const int		partialSize	= 501;
				string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

				useGroup->addChild(new BufferMapWriteCase			(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),	"", target, hint, size, verify));
				useGroup->addChild(new BufferPartialMapWriteCase	(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, verify));
			}
		}

		// .usage_hints
		{
			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
			mapWriteGroup->addChild(hintsGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
				{
					deUint32		target		= bufferTargets[targetNdx];
					deUint32		hint		= usageHints[hintNdx];
					const int		size		= 1019;
					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

					hintsGroup->addChild(new BufferMapWriteCase(m_context, name.c_str(), "", target, hint, size, VERIFY_AS_VERTEX_ARRAY));
				}
			}
		}

		// .invalidate
		{
			tcu::TestCaseGroup* invalidateGroup = new tcu::TestCaseGroup(m_testCtx, "invalidate", "Buffer invalidate");
			mapWriteGroup->addChild(invalidateGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;

				invalidateGroup->addChild(new BufferMapInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
				invalidateGroup->addChild(new BufferMapInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
			}
		}

		// .partial_invalidate
		{
			tcu::TestCaseGroup* invalidateGroup = new tcu::TestCaseGroup(m_testCtx, "partial_invalidate", "Partial invalidate");
			mapWriteGroup->addChild(invalidateGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;

				invalidateGroup->addChild(new BufferMapPartialInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
				invalidateGroup->addChild(new BufferMapPartialInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
			}
		}

		// .explicit_flush
		{
			tcu::TestCaseGroup* flushGroup = new tcu::TestCaseGroup(m_testCtx, "explicit_flush", "Explicit flush");
			mapWriteGroup->addChild(flushGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;

				flushGroup->addChild(new BufferMapExplicitFlushCase(m_context, (string(getBufferTargetName(target)) + "_all").c_str(),		"", target, hint, false,	VERIFY_AS_VERTEX_ARRAY));
				flushGroup->addChild(new BufferMapExplicitFlushCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, true,		VERIFY_AS_VERTEX_ARRAY));
			}
		}

		// .unsynchronized
		{
			tcu::TestCaseGroup* unsyncGroup = new tcu::TestCaseGroup(m_testCtx, "unsynchronized", "Unsynchronized map");
			mapWriteGroup->addChild(unsyncGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;

				unsyncGroup->addChild(new BufferMapUnsyncWriteCase(m_context, getBufferTargetName(target),	"", target, hint));
			}
		}
	}

	// .read_write
	{
		tcu::TestCaseGroup* mapReadWriteGroup = new tcu::TestCaseGroup(m_testCtx, "read_write", "Buffer read and write using glMapBufferRange()");
		addChild(mapReadWriteGroup);

		// .[verify type]
		for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
		{
			VerifyType			verify		= bufferUses[useNdx].verify;
			tcu::TestCaseGroup* useGroup	= new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
			mapReadWriteGroup->addChild(useGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				deUint32		target		= bufferTargets[targetNdx];
				deUint32		hint		= GL_STATIC_DRAW;
				const int		size		= 1019;
				const int		partialOffs	= 17;
				const int		partialSize	= 501;
				string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

				useGroup->addChild(new BufferMapReadWriteCase(m_context, (string(getBufferTargetName(target)) + "_full").c_str(),		"", target, hint, size, 0, size, verify));
				useGroup->addChild(new BufferMapReadWriteCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),	"", target, hint, size, partialOffs, partialSize, verify));
			}
		}

		// .usage_hints
		{
			tcu::TestCaseGroup* hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
			mapReadWriteGroup->addChild(hintsGroup);

			for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
			{
				for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
				{
					deUint32		target		= bufferTargets[targetNdx];
					deUint32		hint		= usageHints[hintNdx];
					const int		size		= 1019;
					string			name		= string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

					hintsGroup->addChild(new BufferMapReadWriteCase(m_context, name.c_str(), "", target, hint, size, 0, size, VERIFY_AS_VERTEX_ARRAY));
				}
			}
		}
	}
}

} // Functional
} // gles3
} // deqp
